local Class = require('lua.lang.Class')
local LuaError = require('lua.vm.LuaError')
local Varargs = require('lua.vm.Varargs')

--- @class LuaValue : Varargs
local LuaValue = Class {
	__extends = Varargs
}

LuaValue.TYPE_INT = -2
LuaValue.TYPE_NONE = -1
LuaValue.TYPE_NIL = 0
LuaValue.TYPE_BOOLEAN = 1
LuaValue.TYPE_LIGHT_USERDATA = 2
LuaValue.TYPE_NUMBER = 3
LuaValue.TYPE_STRING = 4
LuaValue.TYPE_TABLE = 5
LuaValue.TYPE_FUNCTION = 6
LuaValue.TYPE_USERDATA = 7
LuaValue.TYPE_THREAD = 8
LuaValue.TYPE_VALUE = 9
--- @type string[]
LuaValue.TYPE_NAMES = {
	'nil',
	'boolean',
	'lightuserdata',
	'number',
	'string',
	'table',
	'function',
	'userdata',
	'thread',
	'value',
}
LuaValue.CALL = '__call'
LuaValue.ADD = '__add'
LuaValue.SUB = '__sub'
LuaValue.MUL = '__mul'
LuaValue.POW = '__pow'
LuaValue.DIV = '__div'
LuaValue.IDIV = '__idiv'
LuaValue.MOD = '__mod'
LuaValue.LEN = '__len'
LuaValue.CONCAT = '__concat'
LuaValue.INDEX = '__index'
LuaValue.NEWINDEX = '__newindex'
LuaValue.UNM = '__unm'
LuaValue.BNOT = '__bnot'
LuaValue.BAND = '__band'
LuaValue.BOR = '__bor'
LuaValue.BXOR = '__bxor'
LuaValue.SHL = '__shl'
LuaValue.SHR = '__shr'
LuaValue.EQ = '__eq'
LuaValue.LT = '__lt'
LuaValue.LE = '__le'
LuaValue.MODE = '__mode'
LuaValue.METATABLE = '__metatable'
LuaValue.TOSTRING = '__tostring'

--- @param value string | boolean | number
--- @return LuaValue
function LuaValue:valueOf(value)
	error('Not implemented')
end

--- @return number
function LuaValue:type()
	return error('Not implemented')
end

--- @return string
function LuaValue:typeName()
	return error('Not implemented')
end

--- @return string
function LuaValue:toLString()
	return self:typeName() .. ': ' .. tostring(self)
end

--- @param key LuaValue
--- @return LuaValue
function LuaValue:getTable(key)
	return error('Not implemented')
end

--- @return boolean
function LuaValue:isTable()
	return false
end

--- @return boolean
function LuaValue:isBoolean()
	return false
end

--- @return boolean
function LuaValue:isClosure()
	return false
end

--- @return boolean
function LuaValue:isString()
	return false
end

--- @return boolean
function LuaValue:isNil()
	return false
end

--- @return boolean
function LuaValue:isNumber()
	return false
end

--- @param key LuaValue | number
--- @return LuaValue
function LuaValue:get(key)
	return self:getTable(key)
end

--- @param key LuaValue | number | string
--- @param value LuaValue
--- @return void
function LuaValue:set(key, value)
	error('Not implemented')
end

--- @return LuaValue
function LuaValue:getmetatable()
	return nil
end

--- @param key LuaValue | number | string
--- @return LuaValue
function LuaValue:rawget(key)
	error('Not implemented')
end

--- @param key LuaValue | number | string
--- @param value any
--- @return void
function LuaValue:rawset(key, value)
	error('Not implemented')
end

--- @return boolean
function LuaValue:toBoolean()
	return true
end

--- @param val LuaValue
--- @return boolean
function LuaValue:eq_b(val)
	return self == val
end

--- @param val LuaValue
--- @return boolean
function LuaValue:rawEq(val)
	return false
end

--- @param tag string
--- @return LuaValue
function LuaValue:getMetaTag(tag)
	local mt = self:getmetatable()
	if mt == nil then
		return require('lua.vm.LuaNil')._NIL
	end
	return mt:rawget(tag)
end

--- @vararg LuaValue
--- @return LuaValue
function LuaValue:call(...)
	return self:callMt():call(self)
end

--- @param tag string
--- @param reason string
--- @return LuaValue
function LuaValue:checkMetaTag(tag, reason)
	local h = self:getMetaTag(tag)
	if h:isNil() then
		LuaError:new(reason .. self:typeName()):throw()
	end
	return h
end

--- @return LuaValue
function LuaValue:callMt()
	return self:checkMetaTag(self.CALL, 'attempt to call ')
end

--- @param tag string
--- @param op2 LuaValue
--- @return LuaValue
function LuaValue:arithmeticOperation(tag, op2)
	local h = self:getMetaTag(tag)
	if h:isNil() then
		h:getMetaTag(tag)
		if h:isNil() then
			error('attempt to perform arithmetic ' .. 
					tag .. ' on ' .. self:typeName() ..
					' and ' .. op2:typeName())
		end
	end
	return h:call(self, op2)
end

--- @param rhs LuaValue
--- @return LuaValue
function LuaValue:add(rhs)
	return self:arithmeticOperation(self.ADD, rhs)
end

--- @param rhs LuaValue
--- @return LuaValue
function LuaValue:sub(rhs)
	return self:arithmeticOperation(self.SUB, rhs)
end

--- @param rhs LuaValue
--- @return LuaValue
function LuaValue:mul(rhs)
	return self:arithmeticOperation(self.MUL, rhs)
end

--- @param rhs LuaValue
--- @return LuaValue
function LuaValue:pow(rhs)
	return self:arithmeticOperation(self.POW, rhs)
end

--- @param rhs LuaValue
--- @return LuaValue
function LuaValue:div(rhs)
	return self:arithmeticOperation(self.DIV, rhs)
end

--- @param rhs LuaValue
--- @return LuaValue
function LuaValue:iDiv(rhs)
	return self:arithmeticOperation(self.IDIV, rhs)
end

--- @param rhs LuaValue
--- @return LuaValue
function LuaValue:bAnd(rhs)
	return self:arithmeticOperation(self.BAND, rhs)
end

--- @param rhs LuaValue
--- @return LuaValue
function LuaValue:bOr(rhs)
	return self:arithmeticOperation(self.BOR, rhs)
end

--- @param rhs LuaValue
--- @return LuaValue
function LuaValue:bXor(rhs)
	return self:arithmeticOperation(self.BXOR, rhs)
end

--- @param rhs LuaValue
--- @return LuaValue
function LuaValue:shl(rhs)
	return self:arithmeticOperation(self.SHL, rhs)
end

--- @param rhs LuaValue
--- @return LuaValue
function LuaValue:shr(rhs)
	return self:arithmeticOperation(self.SHR, rhs)
end

--- @return LuaValue
function LuaValue:bNot()
	return self:checkMetaTag(self.BNOT, 'attempt to perform bitwise on '):call(self)
end

--- @param rhs LuaValue
--- @return LuaValue
function LuaValue:mod(rhs)
	return self:arithmeticOperation(self.MOD, rhs)
end

--- @return LuaValue
function LuaValue:neg()
	return self:checkMetaTag(self.UNM, 'attempt to perform arithmetic on '):call(self)
end

--- @return LuaValue
function LuaValue:bNot()
	return self:checkMetaTag(self.BNOT, 'attempt to perform bitwise on '):call(self)
end

--- @return LuaValue
function LuaValue:len()
	return self:checkMetaTag(self.LEN, 'attempt to get length of '):call(self)
end

--- @return number
function LuaValue:toNumber()
	return 0
end

--- @return boolean
function LuaValue:isInt()
	return false
end

--- @return number
function LuaValue:toInt()
	return 0
end

--- @return boolean
function LuaValue:isFunction()
	return false
end

--- @param expected string
--- @return void
function LuaValue:argError(expected)
	LuaError:new('bad argument: ' .. expected .. ' expected, got ' .. self:typeName()):throw()
end

--- @return boolean
function LuaValue:checkBoolean()
	self:argError('boolean')
	return false
end

--- @return LuaClosure
function LuaValue:checkClosure()
	self:argError('closure')
	return nil
end

--- @return number
function LuaValue:checkNumber()
	self:argError('number')
	return 0
end

--- @return LuaFunction
function LuaValue:checkFunction()
	self:argError('function')
	return nil
end

--- @return Globals
function LuaValue:checkGlobals()
	self:argError('globals')
	return nil
end

--- @return number
function LuaValue:checkInt()
	self:argError('int')
	return 0
end

--- @return number
function LuaValue:checkInteger()
	self:argError('integer')
	return 0
end

--- @return number
function LuaValue:checkLong()
	self:argError('long')
	return 0
end

--- @return LuaNumber
function LuaValue:checkNumber()
	self:argError('number')
	return nil
end

--- @return string
function LuaValue:checkLString()
	self:argError('string')
	return nil
end

--- @return LuaString
function LuaValue:checkString()
	self:argError('string')
	return nil
end

--- @return LuaTable
function LuaValue:checkTable()
	self:argError('table')
	return nil
end

--- @param i number
--- @return LuaValue
function LuaValue:arg(i)
	return i == 1 and self or require('lua.vm.LuaNil')._NIL
end

--- @return number
function LuaValue:narg()
	return 0
end

--- @return LuaValue
function LuaValue:arg1()
	return self
end

--- @param args Varargs
--- @return Varargs
function LuaValue:invoke(args)
	return self:callMt():invoke(args)
end

--[[
--- @param v LuaValue[]
--- @param offset number
--- @param length number
--- @param more Varargs
--- @return Varargs
function LuaValue:varargsOfFromArray(v, offset, length, more)
	if length == 0 then
		return more
	elseif length == 1 then
		return more:narg() > 0 and
	end
end
]]


return LuaValue